package system

import (
	"gin-admin/common/dto/request"
	"gin-admin/common/dto/response"
	"gin-admin/core"
	"gin-admin/enumeration"
	"gin-admin/global"
	"gin-admin/model"
	serviceRequest "gin-admin/service/dto/request"
	serviceResponse "gin-admin/service/dto/response"
	"gin-admin/utils"

	"github.com/jinzhu/copier"
	"github.com/thoas/go-funk"
	"gorm.io/gorm"
)

type MenuService struct{}

var MenuServiceApp = &MenuService{}

func (ms *MenuService) CreateMenuService(requestData *serviceRequest.CreateMenuRequest) *response.ServiceError {
	err := core.UseTransactionWith(global.GA_DB, func(tx *gorm.DB) *response.ServiceError {
		if err := ms.checkMenuExist(tx, requestData.Name); err != nil {
			return err
		}
		if err := ms.checkParentMenuExists(tx, requestData); err != nil {
			return err
		}
		menu := model.Menu{}
		if err := copier.Copy(&menu, &requestData); err != nil {
			global.GA_LOGGER.Sugar().Errorw("复制数据失败")
			return response.WrapError(response.FailedCode, err.Error())
		}
		result := tx.Create(&menu)
		if err := core.HandelError(result); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return err
	}
	return nil
}

func (ms *MenuService) DeleteMenuService(requestData *request.IDRequest) *response.ServiceError {
	menu, err := ms.getMenuInfoById(global.GA_DB, requestData.ID)
	if err != nil {
		return err
	}
	if err := core.GenericLogicalDelete(global.GA_DB, &menu); err != nil {
		return err
	}
	return nil
}

func (ms *MenuService) ModifyMenuService(requestData *serviceRequest.ModifyMenuRequest) *response.ServiceError {
	db := global.GA_DB
	var menu model.Menu
	_, err := core.GenericQueryById(db, requestData.ID, &menu, func() *response.ServiceError {
		return response.WrapError(response.MenuNotFoundCode, response.GetStatusCode(response.MenuNotFoundCode).Message)
	})
	if err != nil {
		return err
	}
	core.GenericUpdateWithModel(db, &menu, &requestData)
	return nil
}

func (ms *MenuService) GetMenuInfoService(id string) (*serviceResponse.MenuResponse, *response.ServiceError) {
	result, err := ms.getMenuInfoById(global.GA_DB, id)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (ms *MenuService) GetMenuListService(requestData *request.BaseQueryRequest) ([]serviceResponse.MenuResponse, *response.ServiceError) {
	db := global.GA_DB
	var menus []model.Menu
	var menuResponses []serviceResponse.MenuResponse
	condition, err := utils.StructToMapWithoutZero(requestData)
	if err != nil {
		return nil, err
	}

	if err = core.GenericQueryByCondition(db, condition, &menus, func() *response.ServiceError {
		return nil
	}); err != nil {
		return nil, err
	}

	for _, menu := range menus {
		var menuResponse serviceResponse.MenuResponse
		if err := ms.mapMenuToResponse(&menu, &menuResponse); err != nil {
			return nil, err
		}
		menuResponses = append(menuResponses, menuResponse)
	}
	menuTree := ms.buildMenuTree(menuResponses)
	return menuTree, nil
}

func (ms *MenuService) buildMenuTree(menuResponses []serviceResponse.MenuResponse) []serviceResponse.MenuResponse {
	menuMap := make(map[string][]serviceResponse.MenuResponse)
	for _, menuResponse := range menuResponses {
		menuMap[menuResponse.ParentID] = append(menuMap[menuResponse.ParentID], menuResponse)
	}
	var buildMenuTree func(parentId string) []serviceResponse.MenuResponse
	buildMenuTree = func(parentId string) []serviceResponse.MenuResponse {
		children := menuMap[parentId]
		for i := range children {
			children[i].Children = buildMenuTree(children[i].ID)
		}
		return children
	}
	return buildMenuTree("")
}

func (ms *MenuService) getMenuInfoById(db *gorm.DB, id string) (*serviceResponse.MenuResponse, *response.ServiceError) {
	var menuResponse serviceResponse.MenuResponse
	err := core.UseTransactionWith(db, func(tx *gorm.DB) *response.ServiceError {
		var menu model.Menu
		if _, err := core.GenericQueryById(tx, id, &menu, func() *response.ServiceError {
			return response.WrapError(response.MenuNotFoundCode, response.GetStatusCode(response.MenuNotFoundCode).Message)
		}); err != nil {
			return err
		}

		if err := ms.mapMenuToResponse(&menu, &menuResponse); err != nil {
			return err
		}

		children, err := ms.getSubMenusRecursive(db, menu.ID)
		if err != nil {
			return err
		}
		menuResponse.Children = children
		return nil
	})
	if err != nil {
		return nil, err
	}
	return &menuResponse, nil
}

func (ms *MenuService) checkMenuExist(tx *gorm.DB, name string) *response.ServiceError {
	condition := core.BuildQueryCondition("name", name)
	var menu model.Menu
	exists, err := core.CheckDataExistsByCondition(tx, condition, &menu)
	if err != nil {
		return err
	}
	if exists {
		return response.WrapError(response.MenuExistsCode, response.GetStatusCode(response.MenuExistsCode).Message)
	}
	return nil
}

func (ms *MenuService) checkParentMenuExists(tx *gorm.DB, requestData *serviceRequest.CreateMenuRequest) *response.ServiceError {
	if requestData.Level != int(enumeration.MenuLevelOne) && funk.IsEmpty(requestData.ParentID) {
		return response.WrapError(response.MenuParentIdNotEmptyCode, response.GetStatusCode(response.MenuParentIdNotEmptyCode).Message)
	}

	if !funk.IsEmpty(requestData.ParentID) {
		var parentMenu model.Menu
		if _, err := core.GenericQueryById(tx, requestData.ParentID, &parentMenu, func() *response.ServiceError {
			return response.WrapError(response.MenuParentNotFoundCode, response.GetStatusCode(response.MenuParentNotFoundCode).Message)
		}); err != nil {
			return err
		}
	}

	if enumeration.MenuLevel(requestData.Level).MenuLevelString() == "未知" {
		return response.WrapError(response.MenuLevelErrorCode, response.GetStatusCode(response.MenuParentNotFoundCode).Message)
	}
	return nil
}

func (ms *MenuService) mapMenuToResponse(menu *model.Menu, menuResponse *serviceResponse.MenuResponse) *response.ServiceError {
	if err := utils.ModelToStruct(menuResponse, menu); err != nil {
		return err
	}
	menuResponse.StatusName = enumeration.CommonStatus(menuResponse.Status).StatusString()
	menuResponse.LevelName = enumeration.MenuLevel(menuResponse.Level).MenuLevelString()
	return nil
}

func (ms *MenuService) getSubMenusRecursive(db *gorm.DB, parentId string) ([]serviceResponse.MenuResponse, *response.ServiceError) {
	var subMenus []model.Menu
	var subMenuResponses []serviceResponse.MenuResponse
	condition := core.BuildQueryCondition("parent_id", parentId)
	err := core.GenericQueryByCondition(db, condition, &subMenus, func() *response.ServiceError {
		return nil
	})
	if err != nil && err.Code != response.MenuNotFoundCode {
		return nil, err
	}
	for _, subMenu := range subMenus {
		var subMenuResponse serviceResponse.MenuResponse
		if err := ms.mapMenuToResponse(&subMenu, &subMenuResponse); err != nil {
			return nil, err
		}
		children, err := ms.getSubMenusRecursive(db, subMenu.ID)
		if err != nil {
			return nil, err
		}
		subMenuResponse.Children = children
		subMenuResponses = append(subMenuResponses, subMenuResponse)
	}
	return subMenuResponses, nil
}
